package frame

import (
	"net"
	"mylog"
	"bytebuf"
	"fmt"
)

type ReceiveState int

const (
	NoStart   ReceiveState = iota
	Start
	Receiving
)

const MaxFrameLen = 2048

const ByteHead = 0x7E
const ByteEnd = 0x7F

const LenHead = 5
const LenEnd = 2

const IndexFrameCode = 1
const IndexFrameNo = 2
const IndexFrameLenHigh = 3
const IndexFrameLenLow = 4

const CodeLogin = 0x01
const CodeSyncTime = 0x02
const CodeHeartBeat = 0x03
const CodeState = 0x05
const CodeDownloadFileRequest = 0x08
const CodeDownloadState = 0x09

const CodeUniversalAdd = 0x80

type UniversalResult uint8

const (
	UniversalResultSuccess UniversalResult = 0
	UniversalResultFailed                  = 1
)

type DeviceState struct {
	No    uint8
	State uint8
	Info  string
}

type DownloadState uint8

const (
	DownloadStateStart           DownloadState = 0x00
	DownloadStateTransferring    DownloadState = 0x01
	DownloadStateFinish          DownloadState = 0x02
	DownloadStateAbort           DownloadState = 0x03
	DownloadStateFailed          DownloadState = 0x04
	DownloadStateFileNotFound    DownloadState = 0x05
	DownloadStateUnsupportedType DownloadState = 0x06
	DownloadStateExceuteError    DownloadState = 0x07
)

type DeviceStateId uint8

const (
	DeviceStateIdVehicleId = 0x01
)

type FileType uint8

const (
	FileTypeApk FileType = 6
)

var EmptyByteArray = make([]byte, 0)

var frameNo byte = 0

type ReadWriter struct {
	pos      int
	state    ReceiveState
	buf      []byte
	frameLen int
}

func NewFrameReadWriter() ReadWriter {
	a := ReadWriter{
		pos: 0, state: NoStart, buf: make([]byte, MaxFrameLen), frameLen: -1,
	}
	return a
}

func (a *ReadWriter) Read(conn net.Conn, buf []byte, f func(conn net.Conn, frame []byte)) {
	for _, b := range buf {
		a.pos++
		if a.pos > MaxFrameLen || a.state == NoStart {
			a.pos = 0
			a.state = NoStart
		}

		switch a.state {
		case NoStart:
			a.pos = 0
			if b == ByteHead {
				a.state = Start
				a.setByte(b)
			}
		case Start:
			a.setByte(b)
			if a.pos == IndexFrameLenLow {
				a.frameLen = GetFrameLen(a.buf)
				if a.frameLen > MaxFrameLen {
					a.state = NoStart
					mylog.LogE("read frame len exceed max frame len, current:", a.frameLen, "max:", MaxFrameLen)
				} else {
					a.state = Receiving
				}
			}
		case Receiving:
			a.setByte(b)
			if a.pos+1 >= a.frameLen {
				if b == ByteEnd {
					if check(a.buf) {
						f(conn, a.buf[:a.pos+1])
					}
				}
				a.state = NoStart
			}
		default:
		}
	}
}

func (a *ReadWriter) setByte(b byte) {
	a.buf[a.pos] = b
}
func GetFrameCode(frame []byte) byte {
	return frame[IndexFrameCode]
}

func GetFrameNo(frame []byte) byte {
	return frame[IndexFrameNo]
}

func GetFrameBodyLen(frame []byte) int {
	return bytebuf.ToInt(frame[IndexFrameLenLow], frame[IndexFrameLenHigh])
}

func GetFrameLen(frame []byte) int {
	return GetFrameBodyLen(frame) + LenHead + LenEnd
}

func GetFrameCheckSum(frame []byte) byte {
	pos := GetFrameLen(frame) - 2
	return frame[pos]
}

func check(frame []byte) bool {
	end := LenHead + GetFrameBodyLen(frame)

	var ch = ByteHead

	for i := 1; i < end; i++ {
		ch = ch ^ int(frame[i]&0x00FF)
	}

	origin := int(GetFrameCheckSum(frame) & 0x00FF)

	result := ch == origin
	if !result {
		msg := fmt.Sprintf("Error checking frame, origin check sum:%02X origin:%02X", ch, origin)
		mylog.LogE(msg)
	}

	return result
}
func pack(frameCode byte, frameNo byte, content []byte) []byte {
	writeBuf := bytebuf.NewByteBuf()
	writeBuf.WriteByte(ByteHead)
	writeBuf.WriteByte(frameCode)
	writeBuf.WriteByte(frameNo)
	writeBuf.WriteUint16(uint16(len(content)))
	writeBuf.WriteByteArray(content)

	var ch = ByteHead
	for i := 1; i < writeBuf.Pos; i++ {
		ch = ch ^ int(writeBuf.Data[i]&0x00FF)
	}
	writeBuf.WriteByte(byte(ch))
	writeBuf.WriteByte(ByteEnd)
	return writeBuf.GetCurrentData()
}

func PackUniversalResult(requestFrameCode byte, frameNo byte, result UniversalResult, info string) []byte {
	writeBuf := bytebuf.NewByteBuf()
	writeBuf.WriteByte(uint8(result))
	writeBuf.WriteUtf8StringWithLen2(info)
	return pack(requestFrameCode|CodeUniversalAdd, frameNo, writeBuf.GetCurrentData())
}

func PackLoginResult(state int, info string) []byte {
	writeBuf := bytebuf.NewByteBuf()

	writeBuf.WriteByte(byte(state))
	writeBuf.WriteUtf8StringWithLen2(info)

	return pack(CodeLogin, frameNo, writeBuf.GetCurrentData())
}

func PackSyncTime(time int64) []byte {
	writeBuf := bytebuf.NewByteBuf()

	writeBuf.WriteInt64(time)

	return pack(CodeSyncTime, frameNo, writeBuf.GetCurrentData())
}

func PackHeartBeatResult() []byte {
	return pack(CodeHeartBeat, frameNo, EmptyByteArray)
}

func PackDownloadFileRequest(ftpAddress string, ftpPort int, ftpUserName string, ftpPassword string, fileType FileType, ftpDir string) []byte {
	writeBuf := bytebuf.NewByteBuf()

	writeBuf.WriteUtf8StringWithLen1(ftpAddress)
	writeBuf.WriteUint16(uint16(ftpPort))
	writeBuf.WriteUtf8StringWithLen1(ftpUserName)
	writeBuf.WriteUtf8StringWithLen1(ftpPassword)
	writeBuf.WriteUint8(uint8(fileType))
	writeBuf.WriteUtf8StringWithLen1(ftpDir)

	return pack(CodeDownloadFileRequest, frameNo, writeBuf.GetCurrentData())
}
